﻿/*****************************************************************************
 * Hyldahl.Services.TaskProcessing                 Created by: MSH 2008.07.22
 * --------------------------------------------------------------------------
 * FileName: Data\TaskSqlDataProvider.cs
 * --------------------------------------------------------------------------
 * Copyright 2009 Martin Sixhøj Hyldahl
 *****************************************************************************/

using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;

namespace Hyldahl.Services.TaskProcessing.Data
{
    public class TaskSqlDataProvider : TaskDataProvider
    {
        /*****************************************************
         * DATA METHODS
         *****************************************************/

        /// <summary>
        /// Creates the task.
        /// </summary>
        /// <param name="task">The task.</param>
        public override void CreateTask(Task task)
        {
            ValidateTaskForCreation(task);

            SqlConnection   connection  = null;
            SqlCommand      command     = null;

            try
            {
                connection  = new SqlConnection(this.ConnectionString);
                command     = new SqlCommand(
                    "insert into TPS_Task " +
                    "(TaskId, TaskType, TaskData, TaskCreatedDate)" +
                    " values " +
                    "(@TaskId, @TaskType, @TaskData, @TaskCreatedDate)"
                    , connection);

                command.CommandType = CommandType.Text;

                command.Parameters.Add("@TaskId", SqlDbType.UniqueIdentifier).Value     = task.Id;
                command.Parameters.Add("@TaskType", SqlDbType.VarChar).Value            = task.TaskType;
                command.Parameters.Add("@TaskData", SqlDbType.VarChar).Value            = task.Data;
                command.Parameters.Add("@TaskCreatedDate", SqlDbType.DateTime).Value    = task.CreatedDate;

                connection.Open();
                command.ExecuteNonQuery();
            }
            catch (SqlException ex)
            {
                DataException dataEx = new DataException("Could not create new task.", ex);
                throw dataEx;
            }
            finally
            {
                if (command != null)
                    command.Dispose();

                if (connection != null)
                    connection.Dispose();
            }
        }

        /// <summary>
        /// Saves the task result.
        /// </summary>
        /// <param name="task">The task.</param>
        public override void SaveTaskResult(Task task)
        {
            if (task == null)
                throw new ArgumentNullException("task");

            if (!task.IsProcessed)
                throw new ArgumentException("Task has not been processed.", "task");

            if (task.Id == Guid.Empty)
                throw new ArgumentException("Invalid task id.", "task");

            if(!task.ProcessingTime.HasValue || task.ProcessingTime < 0)
                throw new ArgumentException("Invalid processing time.", "task");

            if (!task.CompletedSuccessfully.HasValue)
                throw new ArgumentException("Invalid CompletedSuccessfully value.", "task");

            if (!task.CompletedDate.HasValue)
                throw new ArgumentException("Invalid CompletedDate value.", "task");

            SqlConnection   connection  = null;
            SqlCommand      command     = null;

            try
            {
                connection  = new SqlConnection(this.ConnectionString);
                command     = new SqlCommand(
                    "update TPS_Task set " +
                    " TaskResultData            = @TaskResultData " +
                    ",TaskProcessed             = @TaskProcessed " +
                    ",TaskCompletedSuccessfully = @TaskCompletedSuccessfully " +
                    ",TaskProcessingTime        = @TaskProcessingTime " +
                    ",TaskCompletedDate         = @TaskCompletedDate " +
                    "where TaskId = @TaskId"
                    , connection);

                command.CommandType = CommandType.Text;

                command.Parameters.Add("@TaskId", SqlDbType.UniqueIdentifier).Value         = task.Id;
                command.Parameters.Add("@TaskResultData", SqlDbType.VarChar).Value          = (task.ResultData != null ? (object)task.ResultData : DBNull.Value);
                command.Parameters.Add("@TaskProcessed", SqlDbType.Bit).Value               = task.IsProcessed;
                command.Parameters.Add("@TaskCompletedSuccessfully", SqlDbType.Bit).Value   = task.CompletedSuccessfully;
                command.Parameters.Add("@TaskProcessingTime", SqlDbType.BigInt).Value       = task.ProcessingTime;
                command.Parameters.Add("@TaskCompletedDate", SqlDbType.DateTime).Value      = task.CompletedDate;

                connection.Open();
                command.ExecuteNonQuery();
            }
            catch (SqlException ex)
            {
                DataException dataEx = new DataException("Could not update task results.", ex);
                throw dataEx;
            }
            finally
            {
                if (command != null)
                    command.Dispose();

                if (connection != null)
                    connection.Dispose();
            }
        }

        /// <summary>
        /// Gets the tasks.
        /// </summary>
        /// <returns></returns>
        public override Collection<Task> GetTasks()
        {
            SqlConnection   connection  = null;
            SqlCommand      command     = null;

            try
            {
                connection  = new SqlConnection(this.ConnectionString);
                command     = new SqlCommand("select * from TPS_Task", connection);

                command.CommandType = CommandType.Text;

                return ExecuteTaskCollectionReader(command);
            }
            catch (SqlException ex)
            {
                DataException dataEx = new DataException("Could not get all tasks.", ex);
                throw dataEx;
            }
            finally
            {
                if (command != null)
                    command.Dispose();

                if (connection != null)
                    connection.Dispose();
            }
        }

        /// <summary>
        /// Gets the task.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public override Task GetTask(Guid id)
        {
            if (id == Guid.Empty)
                throw new ArgumentException("Invalid id.", "id");

            SqlConnection   connection  = null;
            SqlCommand      command     = null;

            try
            {
                connection  = new SqlConnection(this.ConnectionString);
                command = new SqlCommand("select * from TPS_Task where TaskId = @taskId", connection);

                command.CommandType = CommandType.Text;

                command.Parameters.Add("@TaskId", SqlDbType.UniqueIdentifier).Value = id;

                return ExecuteTaskReader(command);
            }
            catch (SqlException ex)
            {
                DataException dataEx = new DataException("Could not get task.", ex);
                throw dataEx;
            }
            finally
            {
                if (command != null)
                    command.Dispose();

                if (connection != null)
                    connection.Dispose();
            }

        }

        /// <summary>
        /// Gets the an unprocessed task.
        /// </summary>
        /// <returns></returns>
        public override Task GetUnProcessedTask()
        {
            SqlConnection   connection  = null;
            SqlCommand      command     = null;

            try
            {
                connection  = new SqlConnection(this.ConnectionString);
                command     = new SqlCommand(
                    "declare @taskId uniqueidentifier \n" +
                    "set @taskId = null \n" +
                    "\n"+
                    "select top 1 @taskId = TaskId from TPS_Task \n" +
                    "where TaskProcessed = 0 and (TaskReadDate is null or TaskReadDate > dateadd(n, 15, getutcdate())) \n" +
                    "order by TaskCreatedDate \n" +
                    "\n" +
                    "if @taskId is not null begin \n" +
                    "   update TPS_Task set TaskReadDate = getutcdate() where TaskId = @taskId \n" +
                    "   select * from TPS_Task where TaskId = @taskId \n" +
                    "end \n"
                    , connection);

                command.CommandType = CommandType.Text;

                return ExecuteTaskReader(command);
            }
            catch (SqlException ex)
            {
                DataException dataEx = new DataException("Could not get an unprocessed task.", ex);
                throw dataEx;
            }
            finally
            {
                if (command != null)
                    command.Dispose();

                if (connection != null)
                    connection.Dispose();
            }
        }

        /*****************************************************
         * PRIVATE METHODS
         *****************************************************/

        /// <summary>
        /// Executes the task reader.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns></returns>
        private Task ExecuteTaskReader(SqlCommand command)
        {
            command.Connection.Open();

            using(SqlDataReader reader = command.ExecuteReader())
            {
                if (reader.Read())
                {
                    Task task = new Task();

                    FillTask(reader, task);

                    return task;
                }
                
                return null;
            }
        }

        /// <summary>
        /// Executes the task collection reader.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns></returns>
        private Collection<Task> ExecuteTaskCollectionReader(SqlCommand command)
        {
            command.Connection.Open();

            using(SqlDataReader reader = command.ExecuteReader())
            {
                Collection<Task> tasks = new Collection<Task>();

                while (reader.Read())
                {
                    Task task = new Task();

                    FillTask(reader, task);

                    tasks.Add(task);
                }

                return tasks;
            }
        }

        /// <summary>
        /// Fills the specified <see cref="Task"/> object with data from the specfied <see cref="SqlDataReader"/>.
        /// </summary>
        /// <param name="reader">Data reader to read data from.</param>
        /// <param name="task"><see cref="Task"/> object to fill.</param>
        /// <exception cref="ArgumentNullException">
        /// 	<para><paramref name="reader"/> is null</para>
        /// 	<para> - or - </para>
        /// 	<para><paramref name="task"/> is null</para>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// 	<para><paramref name="reader"/> is closed.</para>
        /// 	<para> - or - </para>
        /// 	<para><paramref name="reader"/> is empty.</para>
        /// </exception>
        private static void FillTask(DbDataReader reader, Task task)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            if (task == null)
                throw new ArgumentNullException("task");

            if (reader.IsClosed)
                throw new ArgumentException("Cannot fill from closed reader.", "reader");

            if (!reader.HasRows)
                throw new ArgumentException("Cannot fill from empty reader.", "reader");

            task.Id             = reader.GetGuid(reader.GetOrdinal("TaskId"));
            task.TaskType       = reader.GetString(reader.GetOrdinal("TaskType"));
            task.Data           = reader.GetString(reader.GetOrdinal("TaskData"));
            task.IsProcessed    = reader.GetBoolean(reader.GetOrdinal("TaskProcessed"));
            task.CreatedDate    = reader.GetDateTime(reader.GetOrdinal("TaskCreatedDate"));

            if (!reader.IsDBNull(reader.GetOrdinal("TaskResultData")))
                task.ResultData = reader.GetString(reader.GetOrdinal("TaskResultData"));

            if(!reader.IsDBNull(reader.GetOrdinal("TaskCompletedSuccessfully")))
                task.CompletedSuccessfully = reader.GetBoolean(reader.GetOrdinal("TaskCompletedSuccessfully"));

            if(!reader.IsDBNull(reader.GetOrdinal("TaskProcessingTime")))
                task.ProcessingTime = reader.GetInt64(reader.GetOrdinal("TaskProcessingTime"));

            if(!reader.IsDBNull(reader.GetOrdinal("TaskReadDate")))
                task.ReadDate = reader.GetDateTime(reader.GetOrdinal("TaskReadDate"));

            if (!reader.IsDBNull(reader.GetOrdinal("TaskCompletedDate")))
                task.CompletedDate = reader.GetDateTime(reader.GetOrdinal("TaskCompletedDate"));   
        }

    }
}
